/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.TreeMap;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.datatype.XMLGregorianCalendar;

import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import org.openrdf.model.impl.URIImpl;

import be.destin.rdf.changes.Status;
import be.destin.rdf.changes.StatusInterface;
import be.destin.util.Util;

public class Term implements Literal, StatusInterface {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private static final URI TERM_XML_DATATYPE = new URIImpl("xs:string");

	private String language;
	private String value;
	private String source;
	private Status status;
	private TermFormat format;

	public Term() {
		super();
	}  
 
	public Term(String key, TermFormat format, String value, String source) {
		super();
		if (key == null) key = TermList.anyLanguage;
		this.language = key.trim();
		//if (format == null) this.format = TermFormat.REGULAR; NO, NO: we need a way to know that format is temporarily UNKWOWN
		//else
		this.format = format;
		this.value = value.trim();
		if (source != null) {
			this.source = source.trim();
			if (this.source.isEmpty()) this.source = null;
		}
	}  

	public Term getThis() {
		return this;
	}

	/**
	 * @return the key
	 */
	@XmlAttribute
	public String getLang() {
		return this.language;
	}

	/**
	 * @return the source of the term
	 */
	@XmlAttribute
	public String getSource() {
		if (this.source == null) return null;
		if (this.source.isEmpty()) return null;
		return this.source;
	}

	/**
	 * @return the value
	 */
	@XmlValue
	public String getValue() {
		return this.value;
	}

	/**
	 * @param key the key to set
	 */
	public void setLang(String key) {
		if (key == null) this.language = null;
		else this.language = key.trim();
	}

	/**
	 * @param key the source to set
	 */
	public void setSource(String key) {
		if (key == null) this.source = null;
		else {
			this.source = key.trim();
			if (this.source.isEmpty()) {
				this.source = null;
			}
		}
	}
	/**
	 * @param value the value to set
	 */
	public void setValue(String value) {
		if (value == null) this.value = null;
		else this.value = value.trim();
	}
	
	public int checkDuplicatedLabel(TreeMap<String,String> allLabels,boolean languageSensitive,Concept parent,String field) {
		if (this.value.isEmpty()) return 0;
		int total = 0;
		String toCheck = Util.noAccent(this.value.toUpperCase()) + (languageSensitive?'/'+this.language:"");
		String currAbout = parent.getScheme_About();
		String previous = allLabels.get(toCheck);
		if (previous != null) {
			if (previous.equals(currAbout)) {
				if (languageSensitive) {
					parent.addError("Term "+this.toString()+" is duplicated in "+currAbout+", field "+field);
					total = 1;
				}
			} else {
				parent.addError("Term "+this.toString()+" in "+currAbout+", field "+field+" is duplicate of "+previous);
				allLabels.put(toCheck, currAbout); // So Previous is nearer...
				total = 1;
			}
		}
		else {
			allLabels.put(toCheck, currAbout);
		}
		return total;
	}

	public String toHtml(String template) {
		String result = template;
		String lang = this.getLang();
		String value = this.getValue();
		if (value == null) value = "";
		String source = this.getSource();
		if (source == null) source = "";
		String text = this.toHtmlValue();
		result = Util.replaceAllNoRegExp(result, "[value]",Util.escapeHTMLEntities(value));
		result = Util.replaceAllNoRegExp(result, "[source]",Util.escapeHTMLEntities(source));
		result = Util.replaceAllNoRegExp(result, "[text]",text);
		result = Util.replaceAllNoRegExp(result, "[lang]",lang);
		return result;
	}

	public String toHtml() {
		return toHtmlValue()+toHtmlSource();
	}

	public String toRdfLangValue(String property) {
		if (this.value == null || this.value.isEmpty()) return "";
		String xmLang = "";
		if (this.language != null && !this.language.isEmpty()) {
			xmLang = " xml:lang=\""+this.language+"\"";
		}
		if (format == null || format == TermFormat.REGULAR) {
			return "<"+property+xmLang+">"+Util.escapeHTMLEntities(this.value)+"</"+property+">";
		} else if (format == TermFormat.URL || format == TermFormat.REL) {
			return "<"+property+xmLang+" rdf:resource=\""+Util.escapeHTMLEntities(this.value)+"\"/>";
		} else if (format == TermFormat.HTML) {
			return "<"+property+xmLang+"><![CDATA["+this.value+"]]></"+property+">";
		} else return ""; // unsupported format ???
	}

	public String toHtmlValue() {
		if (this.value == null) return "";
		if (format == null || format == TermFormat.REGULAR) {
			return Util.escapeHTMLEntities(this.applyRTL());
		} else if (format == TermFormat.URL || format == TermFormat.REL) {
			return "<a href=\""+this.value+"\" target=\"_blank\">"+this.value+"</a>";
		} else if (format == TermFormat.HTML) {
			return this.value; // Strictly NO change, RTL indicators are supposed to be embedded in the HTML
		} else return this.format+":"+Util.escapeHTMLEntities(this.value); // unsupported format ???
	}

	public String toHtmlSource() {
		return Util.escapeHTMLEntities(stringSource());
	}

	public String applyRTL() {
		String value = this.getValue();
		if (value == null) value = "";
		String lang = this.getLang();
		if (TermList.isRTL(lang)) {
			return /*Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING*/'\u202B'+value+/*Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT*/'\u202C';
		}
		return value;
	}
	
	public String toGraphViz(int width) {
		if (this.format == TermFormat.HTML) {
			return this.getValue(); // TODO:A TESTER !!!
		} else {
			String value = this.getValue();
			if (value == null) value = "";
			String lang = this.getLang();
			String eol = "\\n";
			if (TermList.isRTL(lang)) {
				eol = /*Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT*/'\u202C'
					+eol+/*Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING*/'\u202B';
				return /*Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING*/'\u202B'
				+Util.escapeHTMLEntities(Util.glue(Util.wordWrap(value,width),eol))
				+/*Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT*/'\u202C';
			}
			else return Util.escapeHTMLEntities(Util.glue(Util.wordWrap(value,width),eol));
		}
	}

	@XmlTransient
	public boolean isRTL() {
		return TermList.isRTL(this.getLang());
	}

	@XmlAttribute(name="status")
	public String getStatus() {
		if (status == null) return null;
		return status.toString();
	}

	public void setStatus(String aStatus) {
		if (aStatus == null || aStatus.isEmpty()) {
			this.status = null;
			return;
		}
		if (aStatus.startsWith("status_")) {
			aStatus = aStatus.substring("status_".length());
		}
		this.status = Status.valueOf(aStatus);
	}

	@XmlTransient
	public Status getCurrentStatus() {
		return status;
	}

	public void setCurrentStatus(Status status) {
		if (status == null) return;
		this.status = status;
	}

	public String toRdfObjectUrl() {
		return "&literallanguage="+this.language+"&literal="+Util.escapeHTMLEntities(this.value);
	}

	public String toJavaScriptParameters() {
		return ",literallanguage:'"+this.language+"',literal:'"+Util.escapeJavaScript(this.value)+"'"; // JavaScript string encoding????
	}


	@XmlTransient
	 public String getLabel() {
         return value;
     }

	@XmlTransient
	 public String getIndexLABEL() {
		if (this.value == null || this.value.isEmpty()) return null;
		String LABEL = this.value;
		if (this.format == TermFormat.HTML) LABEL = Util.noHTMLtag(this.value);
		LABEL = Util.noAccent(LABEL.toUpperCase().trim());
		if (LABEL.isEmpty()) return null;
        return LABEL;
    }

	@XmlTransient
     public String getLanguage() {
         return language;
     }


	@XmlTransient
	 public URI getDatatype() {
         return TERM_XML_DATATYPE;
     }
     // Overrides Object.equals(Object), implements Literal.equals(Object)
     @Override
     /** 
 	public  boolean equals (Object o) {
 		if (! (o instanceof Term )) return false;
 		Term x = (Term) o;
 		return ((x.language.equals(this.language)) && (x.value.equals(this.value)));
 	}
    **/
     public boolean equals(Object o) {
         if (this  == o) {
             return true;
         }

         if (o instanceof  Literal) {
             Literal other = (Literal) o;

             // Compare language tags
             if (language == null) {
                 if (other.getLanguage() != null) {
                     return false;
                 }
             } else {
                 if (!language.equals(other.getLanguage())) {
                     return false;
                 }
             }

             // Compare labels
             if (!(value+stringSource()).equals(other.getLabel())) {
                 return false;
             }

             return true;
         }

         return false;
     }

     // overrides Object.hashCode(), implements hashCode()
     @Override
     public int hashCode() {
         return value.hashCode();
     }

     /**
      * Returns the label of the literal.
      */
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(value.length() * 2);

         if (this.format != null && this.format == TermFormat.URL) {
        	 sb.append('<');
        	 sb.append(value);
        	 sb.append(stringSource());
        	 sb.append('>');
         } else {
        	 sb.append('"');
        	 sb.append(value);
        	 sb.append(Util.escapeJavaScript(stringSource()));
        	 sb.append('"');
         }
         if (language != null) {
             sb.append('@');
             sb.append(language);
         }

         return sb.toString();
     }

     public String toSimpleString() {
 		if (this.value == null || this.value.isEmpty()) return "";
		if (this.format == TermFormat.HTML) return Util.noHTMLtag(this.value);
        return this.value;
     }

     public String stringSource() {
    	 if (source == null || source.isEmpty()) return "";
         return " ["+source+"]";
     }

     public String stringValue() {
 		if (this.value == null) return stringSource();
		return this.value+stringSource();
     }

     public boolean booleanValue() {
         return XMLDatatypeUtil.parseBoolean(getLabel());
     }

     public byte byteValue() {
         return XMLDatatypeUtil.parseByte(getLabel());
     }

     public short shortValue() {
         return XMLDatatypeUtil.parseShort(getLabel());
     }

     public int intValue() {
         return XMLDatatypeUtil.parseInt(getLabel());
     }

     public long longValue() {
         return XMLDatatypeUtil.parseLong(getLabel());
     }

     public float floatValue() {
         return XMLDatatypeUtil.parseFloat(getLabel());
     }

     public double doubleValue() {
         return XMLDatatypeUtil.parseDouble(getLabel());
     }

     public BigInteger integerValue() {
         return XMLDatatypeUtil.parseInteger(getLabel());
     }

     public BigDecimal decimalValue() {
         return XMLDatatypeUtil.parseDecimal(getLabel());
     }

     public XMLGregorianCalendar calendarValue() {
         return XMLDatatypeUtil.parseCalendar(getLabel());
     }

 	@XmlAttribute(name="format")
	public String getFormat() {
 		if (this.format == null) return null;
		return this.format.toString();
	}

 	@XmlTransient
	public TermFormat getCurrentFormat() {
		return this.format;
	}

	public TermFormat getCurrentFormat(TermFormat defaultFormat) {
		if (format == null) return defaultFormat;
		return format;
	}

	public void setFormat(String aFormat) {
		if (aFormat == null || aFormat.isEmpty()) {
			this.format = null;
			return;
		}
		if (aFormat.startsWith("format_")) {
			aFormat = aFormat.substring("format_".length());
		}
		this.format = TermFormat.valueOf(aFormat);
	}

	public void setCurrentFormat(TermFormat aFormat) {
		this.format = aFormat;
	}
}
